using System;
using System.Collections.Generic;
using System.Reflection;
using Unity.Extensions;
using Xunit;

namespace Unity.Extensions
{
    public class GenericMethodsVirtualInterceptorTest
    {
        [Fact]
        public void GenericMethod()
        {
            Recorder.Records.Clear();
            RecordingHandler handler = new RecordingHandler();
            MethodBase method = typeof(NonGenericSpy).GetMethod("GenericMethod");
            Dictionary<MethodBase, List<IInterceptionHandler>> dictionary = new Dictionary<MethodBase, List<IInterceptionHandler>>();
            List<IInterceptionHandler> handlers = new List<IInterceptionHandler> { handler };
            dictionary.Add(method, handlers);

            NonGenericSpy result = WrapAndCreateType<NonGenericSpy>(dictionary);
            result.GenericMethod(21);

            Assert.Equal(3, Recorder.Records.Count);
            Assert.Equal("Before Method", Recorder.Records[0]);
            Assert.Equal("In method with data 21", Recorder.Records[1]);
            Assert.Equal("After Method", Recorder.Records[2]);
        }

        [Fact]
        public void ReturnsDataOfGenericType()
        {
            Recorder.Records.Clear();
            RecordingHandler handler = new RecordingHandler();
            MethodBase method = typeof(NonGenericSpy).GetMethod("GenericReturn");
            Dictionary<MethodBase, List<IInterceptionHandler>> dictionary = new Dictionary<MethodBase, List<IInterceptionHandler>>();
            List<IInterceptionHandler> handlers = new List<IInterceptionHandler> { handler };
            dictionary.Add(method, handlers);

            NonGenericSpy result = WrapAndCreateType<NonGenericSpy>(dictionary);
            int value = result.GenericReturn<int, double>(256.9);

            Assert.Equal(3, Recorder.Records.Count);
            Assert.Equal("Before Method", Recorder.Records[0]);
            Assert.Equal("In method with data 256.9", Recorder.Records[1]);
            Assert.Equal("After Method", Recorder.Records[2]);
            Assert.Equal(default(int), value);
        }

        [Fact]
        public void WhereClauseOnMethod()
        {
            Recorder.Records.Clear();
            RecordingHandler handler = new RecordingHandler();
            MethodBase method = typeof(NonGenericSpy).GetMethod("WhereMethod");
            Dictionary<MethodBase, List<IInterceptionHandler>> dictionary = new Dictionary<MethodBase, List<IInterceptionHandler>>();
            List<IInterceptionHandler> handlers = new List<IInterceptionHandler> { handler };
            dictionary.Add(method, handlers);

            NonGenericSpy result = WrapAndCreateType<NonGenericSpy>(dictionary);
            result.WhereMethod(new Foo());

            Assert.Equal(3, Recorder.Records.Count);
            Assert.Equal("Before Method", Recorder.Records[0]);
            Assert.Equal("In method with data " + typeof(Foo).FullName, Recorder.Records[1]);
            Assert.Equal("After Method", Recorder.Records[2]);
        }

        static T WrapAndCreateType<T>(IEnumerable<KeyValuePair<MethodBase, List<IInterceptionHandler>>> handlers,
                                      params object[] ctorArgs)
        {
            Type wrappedType = VirtualInterceptor.WrapClass(typeof(T));
            ILEmitProxy proxy = new ILEmitProxy(handlers);
            List<object> wrappedCtorArgs = new List<object> { proxy };
            wrappedCtorArgs.AddRange(ctorArgs);
            return (T)Activator.CreateInstance(wrappedType, wrappedCtorArgs.ToArray());
        }

        public class Foo : IFoo {}

        public interface IFoo {}

        public class NonGenericSpy
        {
            public virtual void GenericMethod<T>(T data)
            {
                Recorder.Records.Add("In method with data " + data);
            }

            public virtual T GenericReturn<T, T1>(T1 data)
            {
                Recorder.Records.Add("In method with data " + data);
                return default(T);
            }

            public virtual void WhereMethod<T>(T data)
                where T : class, IFoo
            {
                Recorder.Records.Add("In method with data " + data);
            }
        }
    }
}